//
//	HOME AUTOMATION GATEWAY PROJECT
//
//  (c) 2008 mocom software GmbH & Co KG
//	for European Microsoft Innovation Center
//
//  $Author: volker $
//  $Date: 2009-05-05 18:15:56 +0200 (Di, 05. Mai 2009) $
//  $Revision: 339 $
//
//  Microsoft dotNetMF Project
//  Copyright ©2001,2002,2003,2004 Microsoft Corporation
//  One Microsoft Way, Redmond, Washington 98052-6399 U.S.A.
//  All rights reserved.
//  MICROSOFT CONFIDENTIAL
//
//-----------------------------------------------------------------------------
#include <tinyhal.h>
#include "at91_i2c.h"

#define PULLUP_MODE		RESISTOR_DISABLED
//#define PULLUP_MODE			RESISTOR_PULLUP

#define SET_INPUT			0
#define SET_OUTPUT			(!SET_INPUT)

#if DEBUG
#define DEBUG_PRINT			hal_printf
#define DUMP_REG			i2c_print_reg
#define SPI_PRINT_REG(x)	hal_printf( #x "=%8.8lx\r\n",x)
#else
#define DEBUG_PRINT
#define DUMP_REG
#define SPI_PRINT_REG(x)
#endif
/***************************************************************************/

#if defined(ADS_LINKER_BUG__NOT_ALL_UNUSED_VARIABLES_ARE_REMOVED)
#pragma arm section zidata = "g_AT91_I2C_Driver"
#endif

AT91_I2C_Driver g_AT91_I2C_Driver;

#if defined(ADS_LINKER_BUG__NOT_ALL_UNUSED_VARIABLES_ARE_REMOVED)
#pragma arm section zidata
#endif

//--//
static AT91S_TWI 				*twi = (AT91S_TWI *) AT91C_BASE_TWI;
static I2C_HAL_XACTION*       	m_currentXAction;
static I2C_HAL_XACTION_UNIT*  	m_currentXActionUnit;
static int						aborted;

//------------------------------------------------------------------------------
//         Internal definitions
//------------------------------------------------------------------------------
/// \internal Calculates the value of the IADRSZ field of the MMR register of a
/// TWI peripheral given the size of the slave internal address in bytes.
#define TWI_MMR_IADRSZ(size)    (((size) & 0x3) << 8)

/// \internal Calculates the value of the DADR field of the MMR register of a
/// TWI peripheral given the slave address.
#define TWI_MMR_DADR(address)   (((address) & 0x7F) << 16)

/// \internal Calculates the value of the IADR field of the IADR register of a
/// TWI peripheral, given the slave internal address.
#define TWI_IADR_IADR(address)  ((address) & 0x00FFFFFF)

/// \internal Returns the start command to perform depending on the number of
/// bytes to transmit.
#define TWI_START(bytes) \
    (((bytes == 1) ? AT91C_TWI_START | AT91C_TWI_STOP : AT91C_TWI_START))


/// \internal Calculates the value for the CHDIV and CLDIV field given the
/// desired operating frequency and the system master clock. This macro assumes
/// the CKDIV field value equals one.
#define TWI_CWGR_DIV(Fmck, Ftwi)    (((Fmck / Ftwi) - 3) / 2)

/// Calculates the value of the TWI_CWGR register of a twi peripheral given the
/// system frequency and the desired operating frequency (both in Hz).
#define TWI_CWGR(Fmck, Ftwi) \
    ((1 << 16) | (TWI_CWGR_DIV(Fmck, Ftwi) << 8) | TWI_CWGR_DIV(Fmck, Ftwi))


static void i2c_print_reg(char *msg)
{
	hal_printf("%s\r\n",msg);
	SPI_PRINT_REG(twi);
	SPI_PRINT_REG(twi->TWI_SR);
	SPI_PRINT_REG(twi->TWI_MMR);
	SPI_PRINT_REG(twi->TWI_IADR);
	SPI_PRINT_REG(twi->TWI_CWGR);
	SPI_PRINT_REG(twi->TWI_IMR);
}

BOOL AT91_I2C_Driver::Initialize()
{
    AT91_PMC &pmc = AT91::PMC();
    pmc.EnablePeriphClock(AT91C_ID_TWI);

	// TWI: SCL,SDA
	CPU_GPIO_DisablePin(AT91_GPIO_Driver::PA7,PULLUP_MODE,SET_INPUT,GPIO_ALT_MODE_1);
	CPU_GPIO_DisablePin(AT91_GPIO_Driver::PA8,PULLUP_MODE,SET_OUTPUT,GPIO_ALT_MODE_1);

	// Disable & reset the TWI
    twi->TWI_CR = AT91C_TWI_MSDIS | AT91C_TWI_SWRST;

	CPU_INTC_ActivateInterrupt( AT91C_ID_TWI, AT91_I2C_Driver::ISR, (void*)(size_t)0);

	//--DEBUG_PRINT("I2C: ENABLED\r\n");

	return TRUE;
}

BOOL AT91_I2C_Driver::Uninitialize()
{
	//AT91_PMC_DisablePeriphClock(AT91C_ID_TWI);
	//--DEBUG_PRINT("I2C: DISABLED\r\n");

    return TRUE;
}

UINT32 AT91_I2C_Driver::PopByte(I2C_HAL_XACTION_UNIT* unit, unsigned shift)
{
	I2C_WORD *data;

	data = unit->m_dataQueue.Pop();

    ++unit->m_bytesTransferred;
    --unit->m_bytesToTransfer;

	return ((UINT32)(*data)) << shift;
}

void AT91_I2C_Driver::PushByte(I2C_HAL_XACTION_UNIT* unit, unsigned val)
{
	I2C_WORD *data;

	data = unit->m_dataQueue.Push();

	*data = val;

    ++unit->m_bytesTransferred;
    --unit->m_bytesToTransfer;
}

void AT91_I2C_Driver::XActionStart( struct I2C_HAL_XACTION* xAction, bool repeatedStart )
{
	I2C_HAL_XACTION_UNIT* 	unit;
	UINT32					iadr,
							iadrsize,
							readwrite,
							cldiv,
							chdiv,
							ckdiv,
							rate;
	volatile UINT32			garb;
	unsigned 				i;

	m_currentXAction = xAction;
	aborted = 0;

	//Initialize();
	
	// set baudrate
	rate = ((UINT32)xAction->m_clockRate) << 1;	
	
	if (rate >= 100) {		
		ckdiv = 1;
	} else if (rate >= 50) {
		ckdiv = 2;
	} else if (rate >= 25) {
		ckdiv = 3;
	} else {
		ckdiv = 4;
	}
		
	if (xAction->m_address == (0x52 >> 1)) {
		// asymetric clk for TPM device
		// low time twice of high time
		cldiv = (((SYSTEM_PERIPHERAL_CLOCK_HZ / (((rate*2)/3)*2*1000)) - 4) / (1<<ckdiv));
		chdiv = (((SYSTEM_PERIPHERAL_CLOCK_HZ / (((rate*4)/3)*2*1000)) - 4) / (1<<ckdiv));
	} else {
		chdiv = cldiv = (((SYSTEM_PERIPHERAL_CLOCK_HZ / (rate*2*1000)) - 4) / (1<<ckdiv));
	}
	
	if (cldiv > 255)
		cldiv = 255;
	if (chdiv > 255)
		chdiv = 255;
	
	twi->TWI_CWGR = (cldiv) | (chdiv<<8) | (ckdiv << 16);
	
	//DEBUG_PRINT("I2C: xActionStart brate=%d m_clockRate=%d\r\n",brate,xAction->m_clockRate);

	// the AT91SAM9261 does NOT fully support a repeated start condition. It has, in contrast,
	// the ability to send up to 3-bytes before starting a read action. Thats effectivly what
	// you want to achive with repeated start.
	// To make it work here, we look at the transaction units in xAction-> If there are
	// 	- two units,
	//	- first is a write, not longer than 3 bytes and
	//	- second is a read with a length other than 0
	// we use the 3-byte/reapeated start proceedure.
	// In all other cases, handle units as individual read or write action units.
	//
	if (xAction->m_current == 0 &&
			xAction->m_numXActionUnits == 2 &&
			xAction->m_xActionUnits[0]->m_bytesToTransfer <= 3 &&
			!xAction->m_xActionUnits[0]->IsReadXActionUnit() &&
			xAction->m_xActionUnits[1]->m_bytesToTransfer &&
			xAction->m_xActionUnits[1]->IsReadXActionUnit()) {
		// get units
		unit = xAction->m_xActionUnits[0];
		m_currentXActionUnit = xAction->m_xActionUnits[1];
		// make sure PAL will know that both units have been
		// processed
		xAction->m_current = 2;
		// get iadrsize & data
		iadrsize = unit->m_bytesToTransfer;
		for (iadr=0,i=0;i<iadrsize;i++) {
			iadr |= PopByte(unit,i);
		}
		// action to start is a read (the write of iadr is implicit)
		readwrite = AT91C_TWI_MREAD;
		//--DEBUG_PRINT("I2C: xActionStart smart 0x%x wr=%d rd=%d\r\n",xAction->m_address,iadrsize,m_currentXActionUnit->m_bytesToTransfer);
	} else {
		iadr = iadrsize = 0;
		m_currentXActionUnit = xAction->m_xActionUnits[xAction->m_current++];
		readwrite = m_currentXActionUnit->IsReadXActionUnit() ? AT91C_TWI_MREAD : AT91C_TWI_MWRITE;
		//--DEBUG_PRINT("I2C: xActionStart normal 0x%x dir=%s size=%d\r\n",xAction->m_address,readwrite == AT91C_TWI_MWRITE ? "WR" : "RD",m_currentXActionUnit->m_bytesToTransfer);
	}

	GLOBAL_LOCK(irq);
	
	// enable TWI
	twi->TWI_CR = AT91C_TWI_MSEN;

	// Set address, I2C address and write direction
    twi->TWI_MMR = TWI_MMR_DADR(xAction->m_address)
                   | TWI_MMR_IADRSZ(iadrsize)
                   | readwrite;
	// set internal address
    twi->TWI_IADR = TWI_IADR_IADR(iadr);
	garb = twi->TWI_SR;
	// start transaction
	if (readwrite == AT91C_TWI_MWRITE) {
		// WRITE, enable isr
		twi->TWI_IER = AT91C_TWI_INT_ON_NACK | AT91C_TWI_INT_ON_TXRDY | AT91C_TWI_INT_ON_COMP;
		// send first byte
		twi->TWI_THR = PopByte(m_currentXActionUnit,0);
		twi->TWI_CR = TWI_START(m_currentXActionUnit->m_bytesToTransfer);		
	} else {
		// READ, enable isr
		twi->TWI_IER = AT91C_TWI_INT_ON_NACK | AT91C_TWI_INT_ON_RXRDY | AT91C_TWI_INT_ON_COMP;
		// ###ATTENTION###
		// A dummy read prevents extra byte in our data
		garb = twi->TWI_RHR;
		twi->TWI_CR = TWI_START(m_currentXActionUnit->m_bytesToTransfer);
	}
	//DUMP_REG("I2C: xActionStart");
}

void AT91_I2C_Driver::XActionStop()
{
	// stop isr
	twi->TWI_IDR = AT91C_TWI_INT_ON_NACK | AT91C_TWI_INT_ON_RXRDY | AT91C_TWI_INT_ON_TXRDY | AT91C_TWI_INT_ON_COMP;
	// disable
	twi->TWI_CR = AT91C_TWI_MSDIS;
	// clear internal vars
    m_currentXAction     = NULL;
    m_currentXActionUnit = NULL;
	//DEBUG_PRINT("I2C: xActionStop\r\n");
	//Uninitialize();
}

void AT91_I2C_Driver::GetClockRate( UINT32 rateKhz, UINT8& clockRate, UINT8& clockRate2 )
{
	if (rateKhz == 0)
		rateKhz = 100;
	if (rateKhz > 400)
		rateKhz = 400;
	
	// make it fit into a byte
	clockRate = rateKhz >> 1;
}

void  AT91_I2C_Driver::GetPins( GPIO_PIN& scl, GPIO_PIN& sda )
{
    scl = AT91_GPIO_Driver::PA8;
    sda = AT91_GPIO_Driver::PA7;
}

void AT91_I2C_Driver::ISR( void* arg )
{
	UINT32 				  sr;
    I2C_HAL_XACTION*      xAction = m_currentXAction;
    I2C_HAL_XACTION_UNIT* unit    = m_currentXActionUnit;

	//DEBUG_PRINT("I2C: ISR\r\n");
	sr = twi->TWI_SR;
	while (sr & (AT91C_TWI_TXRDY|AT91C_TWI_RXRDY|AT91C_TWI_TXCOMP)) {
		if (sr & AT91C_TWI_NACK) 
		{
			// nack interrupt
			aborted++;
			twi->TWI_CR = AT91C_TWI_STOP;
			break;
		}
		
		if (sr & AT91C_TWI_TXCOMP) 
		{
			// stop hw
			XActionStop();
			
			// on NACK, abort xAction
			if (aborted) {
				xAction->Signal( I2C_HAL_XACTION::c_Status_Aborted );
			} 	
			
			// xAction complete 
			else if (xAction->ProcessingLastUnit()) {
				xAction->Signal( I2C_HAL_XACTION::c_Status_Completed );
			} 
			else {
				// start next transaction unit
				XActionStart( xAction, true );
			}
			break;
		}
		
		// ###ATTENTION###
		// IF we have a read action with a address write before, we get a AT91C_TWI_TXRDY 
		// status reported in SR. To prevent get a big messup, we have to make sure we 
		// handle AT91C_TWI_TXRDY OR AT91C_TWI_RXRDY but not both in one action unit.
		if (unit->IsReadXActionUnit()) {
			if (sr & AT91C_TWI_RXRDY && (unit->m_bytesToTransfer)) {
				// set stop before last byte read
				if (unit->m_bytesToTransfer == 2) {
					twi->TWI_CR = AT91C_TWI_STOP;
				}
				// read byte
				PushByte(unit,twi->TWI_RHR);
			}
		} else {	
			if (sr & AT91C_TWI_TXRDY) {
				if (unit->m_bytesToTransfer) {
					twi->TWI_THR = PopByte(unit,0);				
				} else {
					twi->TWI_CR = AT91C_TWI_STOP;
				}
			}
		}
		sr = twi->TWI_SR;
	}
}


